﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using WindowsPhoneMVC.Phone;

namespace WindowsPhoneMVC.Extensions.Transitions
{
    [TemplatePart(Name = FirstTemplatePartName, Type = typeof(ContentPresenter))]
    [TemplatePart(Name = SecondTemplatePartName, Type = typeof(ContentPresenter))]
    public class MvcTransitionFrame : MvcFrame
    {
        /// <summary>
        /// The new
        /// <see cref="T:System.Windows.Controls.ContentPresenter"/>
        /// template part name.
        /// </summary>
        private const string FirstTemplatePartName = "FirstContentPresenter";

        /// <summary>
        /// The old
        /// <see cref="T:System.Windows.Controls.ContentPresenter"/>
        /// template part name.
        /// </summary>
        private const string SecondTemplatePartName = "SecondContentPresenter";

        /// <summary>
        /// Determines whether to set the new content to the first or second
        /// <see cref="T:System.Windows.Controls.ContentPresenter"/>.
        /// </summary>
        private bool useFirstAsNew;

        /// <summary>
        /// The first <see cref="T:System.Windows.Controls.ContentPresenter"/>.
        /// </summary>
        private ContentPresenter firstContentPresenter;

        /// <summary>
        /// The second <see cref="T:System.Windows.Controls.ContentPresenter"/>.
        /// </summary>
        private ContentPresenter secondContentPresenter;

        /// <summary>
        /// The old <see cref="T:System.Windows.Controls.ContentPresenter"/>.
        /// </summary>
        private ContentPresenter oldContentPresenter;

        /// <summary>
        /// The new <see cref="T:System.Windows.Controls.ContentPresenter"/>.
        /// </summary>
        private ContentPresenter newContentPresenter;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public MvcTransitionFrame()
        {
            DefaultStyleKey = typeof(MvcTransitionFrame);
            HorizontalContentAlignment = HorizontalAlignment.Stretch;
            VerticalContentAlignment = VerticalAlignment.Stretch;
        }

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes (such as a rebuilding layout pass) call
        /// <see cref="M:System.Windows.Controls.Control.ApplyTemplate"/>.
        /// In simplest terms, this means the method is called just before a UI element displays in an application.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            firstContentPresenter = GetTemplateChild(FirstTemplatePartName) as ContentPresenter;
            secondContentPresenter = GetTemplateChild(SecondTemplatePartName) as ContentPresenter;
            if (Content != null)
            {
                OnContentChanged(null, Content);
            }
        }

        public override void OnNavigationStarted(Navigation.NavigationDirection direction)
        {
            if (useFirstAsNew)
            {
                if (secondContentPresenter != null)
                    secondContentPresenter.CacheMode = new BitmapCache();
            }
            else
            {
                if (firstContentPresenter != null)
                    firstContentPresenter.CacheMode = new BitmapCache();
            }

            base.OnNavigationStarted(direction);
        }

        public override void OnNavigationCompleted(bool successful)
        {
            if (useFirstAsNew)
            {
                if (secondContentPresenter != null)
                    secondContentPresenter.CacheMode = null;
            }
            else
            {
                if (firstContentPresenter != null)
                    firstContentPresenter.CacheMode = null;
            }
            base.OnNavigationCompleted(successful);
        }

        public override void SetApplicationBar(IApplicationBar applicationBar)
        {
            AppBarEnabledLookup.Clear();
            CurrentApplicationBar = applicationBar;
        }

        /// <summary>
        /// Called when the value of the
        /// <see cref="P:System.Windows.Controls.ContentControl.Content"/>
        /// property changes.
        /// </summary>
        /// <param name="oldContent">The old <see cref="T:System.Object"/>.</param>
        /// <param name="newContent">The new <see cref="T:System.Object"/>.</param>
        protected override void OnContentChanged(object oldContent, object newContent)
        {
            base.OnContentChanged(oldContent, newContent);
            var oldElement = oldContent as UIElement;
            var newElement = newContent as UIElement;
            if (firstContentPresenter == null || secondContentPresenter == null || newElement == null)
            {
                return;
            }
            if (useFirstAsNew)
            {
                newContentPresenter = firstContentPresenter;
                oldContentPresenter = secondContentPresenter;
            }
            else
            {
                newContentPresenter = secondContentPresenter;
                oldContentPresenter = firstContentPresenter;
            }
            useFirstAsNew = !useFirstAsNew;
            ITransition oldTransition = null;
            ITransition newTransition = null;
            if (oldElement != null)
            {
                var navigationOutTransition = TransitionService.GetNavigationOutTransition(oldElement);
                TransitionElement oldTransitionElement = null;
                if (navigationOutTransition != null)
                {
                    oldTransitionElement = NavigationDirection == Navigation.NavigationDirection.Forward ? navigationOutTransition.Forward : navigationOutTransition.Backward;
                }
                if (oldTransitionElement != null)
                {
                    oldTransition = oldTransitionElement.GetTransition(oldElement);
                    oldContentPresenter.CacheMode = new BitmapCache();
                    oldContentPresenter.IsHitTestVisible = false;
                }
            }

            var navigationInTransition = TransitionService.GetNavigationInTransition(newElement);
            TransitionElement newTransitionElement = null;
            if (navigationInTransition != null)
            {
                newTransitionElement = NavigationDirection == Navigation.NavigationDirection.Forward ? navigationInTransition.Forward : navigationInTransition.Backward;
            }
            if (newTransitionElement != null)
            {
                newElement.UpdateLayout();
                newTransition = newTransitionElement.GetTransition(newElement);
                newContentPresenter.CacheMode = new BitmapCache();
                newContentPresenter.IsHitTestVisible = false;
            }

            newContentPresenter.Opacity = 0;
            newContentPresenter.Visibility = Visibility.Visible;
            newContentPresenter.Content = newElement;
            oldContentPresenter.Opacity = 1;
            oldContentPresenter.Visibility = Visibility.Visible;
            oldContentPresenter.Content = oldElement;
            if (oldTransition != null)
            {
                if (oldTransition.GetCurrentState() != ClockState.Stopped)
                {
                    oldTransition.Stop();
                }
                oldTransition.Completed += delegate
                {
                    oldTransition.Stop();
                    oldContentPresenter.CacheMode = null;
                    oldContentPresenter.IsHitTestVisible = true;

                    if (CurrentApplicationBar == null)
                        Shell.SetApplicationBar(null);

                    TransitionNewElement(newTransition);
                };
                Dispatcher.BeginInvoke(() => Dispatcher.BeginInvoke(() => oldTransition.Begin()));
            }
            else
            {
                TransitionNewElement(newTransition);
            }
        }

        /// <summary>
        /// Transitions the new <see cref="T:System.Windows.UIElement"/>.
        /// </summary>
        /// <param name="newTransition">The <see cref="T:Microsoft.Phone.Controls.ITransition"/> for the new <see cref="T:System.Windows.UIElement"/>.</param>
        private void TransitionNewElement(ITransition newTransition)
        {
            oldContentPresenter.Visibility = Visibility.Collapsed;
            oldContentPresenter.Content = null;
            if (newTransition == null)
            {
                newContentPresenter.IsHitTestVisible = true;
                newContentPresenter.Opacity = 1;
                Shell.SetApplicationBar(CurrentApplicationBar);
                return;
            }
            if (newTransition.GetCurrentState() != ClockState.Stopped)
            {
                newTransition.Stop();
            }
            newTransition.Completed += (sender, args) =>
                                           {
                                               newTransition.Stop();
                                               newContentPresenter.CacheMode = null;
                                               newContentPresenter.IsHitTestVisible = true;
                                           };
            Dispatcher.BeginInvoke(() =>
                                       {
                                           newContentPresenter.Opacity = 1;
                                           newTransition.Begin();
                                           Shell.SetApplicationBar(CurrentApplicationBar);
                                       });
        }
    }
}
